fix: an ignored node is never pruned, moved or rewritten - #25
Merged
Conversation
`shouldIgnoreNode` only made the walk hop over a node. Everything else still treated it as ordinary: it was counted among the old children, so the tail removal took one node too many, and it could be matched against an incoming node and rewritten into it. The case that surfaced it: a page whose head holds runtime-injected stylesheets (a lazy editor's CSS, a dev server's <style>) that the incoming page never lists. The prune removed all of them mid-navigation — 55 applied sheets down to 0 — and re-attaching a stylesheet makes it pending again, so the page paints unstyled for a frame. Now the predicate is honoured in the three places that touch old children: the count, the walk, and the removal. Ignored means untouched, in place. Contract change: the two existing shouldIgnoreNode tests asserted that an ignored node ended up deleted, which is the opposite of what the option is for. Their expectations now pin the node surviving with its own content, plus a new row for the case above. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The prune now has to ask the caller's predicate before removing an old child, and the walk has to skip ignored nodes rather than rewrite them — 1494 -> 1549 bytes gzip. Trimmed as far as it goes without trading bytes for allocations: settledWalker inherits the walker instead of rebuilding it, and the removal scans back from lastChild with no extra bookkeeping. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
shouldIgnoreNodeonly made the walk hop over a node. Everything else still treated it as an ordinary child:extrawas too high and the tail removal took one node too many;removeChild(oldParent.lastChild), which never consulted the predicate;The case that surfaced it
A page whose
<head>holds runtime-injected stylesheets — a lazily loaded editor's CSS, a dev server's<style>tags — that the incoming page never lists. Measured on a real navigation (Janux docs, playground → docs): 55 applied stylesheets down to 0 mid-swap, then restored a beat later by the app. That round trip is a flash of unstyled content, because a re-attached stylesheet is a new, pending one and the browser paints without it until it resolves.Protecting those nodes with
shouldIgnoreNodedid nothing, because the prune never looked.The fix
The predicate is now honoured in the three places that touch old children: the count, the walk, and the removal. Ignored means untouched, in place — its content, its position and its identity all survive, while its siblings diff as usual.
Contract change
The two existing
shouldIgnoreNodetests asserted that an ignored node ended up deleted, which is the opposite of what the option is named for and of what the README describes. Their expectations now pin the node surviving with its own content. A third row covers the case above: an ignored node the incoming page does not list is kept rather than pruned.If anyone was relying on "ignored ⇒ removed", that was accidental, and there is no way to express "leave this alone" without it.
Size
The library's whole point is being tiny, so: 1494 → 1570 bytes gzip (+76 B), raw 3386 → 3603. The cost is almost entirely the walk-side skip, which is what prevents an ignored node from being rewritten; the count and prune changes are a few bytes.
Tests
bun test→ 141 pass, 0 fail across chrome, firefox and safari (was 138). The 132 unrelated tests are untouched.🤖 Generated with Claude Code